package edu.kpi.pzks2.statistics;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

import edu.kpi.pzks2.graph.Edge;
import edu.kpi.pzks2.graph.GraphBundle.EdgeDescriptor;
import edu.kpi.pzks2.graph.GraphBundle.NodeDescriptor;
import edu.kpi.pzks2.graph.Node;
import edu.kpi.pzks2.queue.QueueBuilder1;
import edu.kpi.pzks2.queue.QueueBuilder2;
import edu.kpi.pzks2.queue.QueueBuilder3;
import edu.kpi.pzks2.simulator.core.SimulationBundle;
import edu.kpi.pzks2.simulator.core.SimulationBundle.SimulationBundleBuilder;
import edu.kpi.pzks2.simulator.core.Simulator;
import edu.kpi.pzks2.simulator.core.strategy.StrategyFactory;
import edu.kpi.pzks2.simulator.impl.PreemptiveStrategy;
import edu.kpi.pzks2.simulator.impl.SimpleRouterImpl;
import edu.kpi.pzks2.simulator.impl.SimpleStrategy;
import edu.kpi.pzks2.simulator.results.SimulationResult;
import edu.kpi.pzks2.statistics.StatisticsBundle.StatisticsBundleBuilder;
import edu.kpi.pzks2.tools.GenerationParams;
import edu.kpi.pzks2.tools.TaskGraphTools;
//import edu.kpi.pzks2.ui.system.System;
import edu.kpi.pzks2.ui.task.TaskFrame.QueueType;
import edu.kpi.pzks2.ui.task.TaskFrame.StrategyType;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;

public class StartStatist {
	public static void main(String[] args) throws ClassNotFoundException, IOException {
		Locale.setDefault(Locale.US);
		java.lang.System.out.println("start");

		//        makeGeneralExperimentt(9,  "res1_1.csv", -1);
		//        java.lang.System.out.println("1_1 done");

		//        makeGeneralExperiment(18, "res1_2.csv", -1);
		//        java.lang.System.out.println("1_2 done");

		//        makeGeneralExperiment(27, "res1_3.csv", 0.2f);
		//        java.lang.System.out.println("1_3 done");
		//        
		//        makeGeneralExperiment(36, "res1_4.csv", 0.2f);
		//        java.lang.System.out.println("1_4 done");

		System.out.println("Start Best");
		makeBestExperiment(27, "best27.csv");
		System.out.println("Done");
	}

	public static void makeGeneralExperiment(int nodeCount, String fileName, float minCon) throws IOException, ClassNotFoundException {
		final FileWriter writer = new FileWriter(new File(fileName));

		StatisticsBundle statBundle;

		if (minCon <= 0) {
			statBundle = initStatistBundleForGeneral(nodeCount).build();
		} else {
			StatisticsBundleBuilder builder = initStatistBundleForGeneral(nodeCount);
			builder.setMinConnectivity(minCon);
			statBundle = builder.build();
		}

		List<List<SingleResult>> res = startGeneralSimulation(statBundle);
		writer.append(printResultForGeneral(statBundle, res));
		writer.close();
	}    

	public static StatisticsBundle.StatisticsBundleBuilder initStatistBundleForGeneral(int nodeCount) throws IOException, ClassNotFoundException {
		final StatisticsBundleBuilder builder = new StatisticsBundleBuilder();

		FileInputStream fis = new FileInputStream(
				new File("D:\\Other\\Yura_recovery\\PZKS\\pzks2\\src\\main\\resources\\tor")); 
		ObjectInputStream ois = new ObjectInputStream(fis); 
		final edu.kpi.pzks2.ui.system.System sysGraphBundle =
				(edu.kpi.pzks2.ui.system.System) ois.readObject(); 
		ois.close();

		Graph <Node, Edge> sysGraph = new UndirectedSparseGraph<Node, Edge>();

		int nodeId = 0;

		for (NodeDescriptor d : sysGraphBundle.getSystem().getNodes()) {
			sysGraph.addVertex(d.getNode());
			if (nodeId < d.getNode().getId()) {
				nodeId = d.getNode().getId();
			}
		}

		for (EdgeDescriptor d : sysGraphBundle.getSystem().getEdges()) {
			sysGraph.addEdge(d.getEdge(), d.getPair().getFirst(), d.getPair().getSecond());
		}

		builder.setSystem(sysGraph);

		final QueueType [] queueBuilders = {QueueType.QUEUE1, QueueType.QUEUE2, QueueType.QUEUE3};
		builder.setQueueBuilders(queueBuilders);

		final StrategyType [] strategyFactories = {StrategyType.SIMPLE, StrategyType.PREEMPTIVE};
		builder.setStrategyFactories(strategyFactories);

		builder.setMinNodeCount(nodeCount);
		builder.setMaxNodeCount(nodeCount);

		return builder;
	}

	public static List<List<SingleResult>> startGeneralSimulation(StatisticsBundle statBundle) {
		final List<List<SingleResult>> generalResult = new ArrayList<List<SingleResult>>();

		for (float connectivity = statBundle.getMinConnectivity();
				connectivity <= statBundle.getMaxConnectivity();
				connectivity += statBundle.getStepConnectivity()) {
			List<List<SingleResult>> results = new ArrayList<List<SingleResult>>();
			for (int r = 0; r < 6; r++) {
				results.add(new ArrayList<SingleResult>());
			}

			for (int i = 0 ; i < statBundle.getSeriesSize(); i++) {
				final GenerationParams genParams =
						new GenerationParams(
								statBundle.getMinNodeWeight(),
								statBundle.getMaxNodeWeight(),
								statBundle.getMinEdgeWeight(),
								statBundle.getMaxEdgeWeight(),
								connectivity,
								statBundle.getMaxNodeCount());

				//                System.out.print("Start GEN");
				final DirectedSparseGraph<Node, Edge> taskGraph = TaskGraphTools.generate(genParams, false);
				//                System.out.println("   Fin GEN");

				int r = 0;

				final int maxCritPath = TaskGraphTools.getMaxCriticalPath(taskGraph);
				final int overallWeight = TaskGraphTools.getOverallWeight(taskGraph);

				for (QueueType queueT : statBundle.getQueues()) {
					for (StrategyType stratT : statBundle.getStrategies()) {
						final SimulationBundleBuilder simBundleBuilder = new SimulationBundleBuilder();
						simBundleBuilder.setChanelCount(1);
						simBundleBuilder.setDuplex(false);
						simBundleBuilder.setLinkSpeed(1);
						simBundleBuilder.setProductivity(1);
						simBundleBuilder.setPackageMode(false);

						simBundleBuilder.setSystem(statBundle.getSystem());
						simBundleBuilder.setTask(taskGraph);

						switch (queueT) {
						case QUEUE1: simBundleBuilder.setQueueBuilder(new QueueBuilder1()); break;
						case QUEUE2: simBundleBuilder.setQueueBuilder(new QueueBuilder2()); break;
						case QUEUE3: simBundleBuilder.setQueueBuilder(new QueueBuilder3()); break;
						default: break;
						}

						final SimulationBundle simBundle = simBundleBuilder.buildBundle();

						StrategyFactory strategyFactory = null;
						switch (stratT) {
						case SIMPLE: strategyFactory = new SimpleStrategy.SimpleStrategyFactory(); break;
						case PREEMPTIVE: strategyFactory = new PreemptiveStrategy.PreemptiveStrategyFactory(simBundle);
						default: break;
						}

						final Simulator simulator =
								new Simulator(simBundle, strategyFactory, new SimpleRouterImpl.SimpleRouterFactory());

						final SimulationResult simRes = simulator.start();
						results.get(r).add(
								analiseResult(simRes, statBundle.getSystem().getVertexCount(), taskGraph,
										maxCritPath, overallWeight));

						r++;
						//                        java.lang.System.out.println(simRes);
					}
				}
				java.lang.System.out.print(i);
			}
			java.lang.System.out.println();

			final List<SingleResult> res = new ArrayList<SingleResult>();
			for (List<SingleResult> resList : results) {
				res.add(SingleResult.getAverage(resList));
			}
			generalResult.add(res);
		}

		return generalResult;
	}

	public static SingleResult analiseResult(
			final SimulationResult simRes,
			final int procCount,
			final Graph<Node, Edge> graph,
			final int maxCritPath,
			final int overallWeight) {
		int time = simRes.getProcHistories().get(0).getIntervals().size();

		final float accl = (float) overallWeight / (float) time;
		final float eff = (float) accl / (float) procCount;
		final float effAlgo = (float) maxCritPath / (float) time;

		//        if (maxCritPath == 0 || effAlgo == 0.5f) {
		//            java.lang.System.out.println("a");
		//        }

		return new SingleResult(time, accl, eff, effAlgo, overallWeight);
	}

	public static String printResultForGeneral(
			final StatisticsBundle statBundle, 
			final List<List<SingleResult>> res) {
		StringBuilder str = new StringBuilder();

		// Time
		str.append("Time parallel\n");
		str.append("con\tA1\tB1\tA2\tB2\tA3\tB3\n");

		final int [] timeBest = new int [6];

		float con = statBundle.getMinConnectivity();
		for (List<SingleResult> line : res) {
			str.append(String.format("%.2f\t", con));

			SingleResult minTime = Collections.min(line, new Comparator<SingleResult>() {
				public int compare(SingleResult o1, SingleResult o2) {
					return Float.compare(o1.getTime(), o2.getTime());
				}
			});

			int i = 0;
			for (SingleResult cell : line) {
				if (Float.compare(cell.getTime(), minTime.getTime()) == 0) {
					str.append("*");
					timeBest[i]++;
				}

				str.append(String.format("%.3f\t", cell.getTime()));
				i++;
			}
			str.append("\n");
			con += statBundle.getStepConnectivity();
		}

		str.append("best\t");
		for (int best : timeBest) {
			str.append(best + "\t");
		}

		// Time
		str.append("\n\nTime single\n");
		str.append("con\tA1\tA2\tA3\tB1\tB2\tB3\n");

		con = statBundle.getMinConnectivity();
		for (List<SingleResult> line : res) {
			str.append(String.format("%.2f\t", con));
			for (SingleResult cell : line) {
				str.append(String.format("%.3f\t", cell.getOverallWeight()));
			}
			str.append("\n");
			con += statBundle.getStepConnectivity();
		}

		// Acceleration
		str.append("\n\nAcceleration\n");
		str.append("con\tA1\tA2\tA3\tB1\tB2\tB3\n");

		con = statBundle.getMinConnectivity();
		for (List<SingleResult> line : res) {
			str.append(String.format("%.2f\t", con));
			for (SingleResult cell : line) {
				str.append(String.format("%.3f\t", cell.getAcceleration()));
			}
			str.append("\n");
			con += statBundle.getStepConnectivity();
		}

		// Effectivity
		str.append("\n\nEffectivity\n");
		str.append("con\tA1\tA2\tA3\tB1\tB2\tB3\n");

		con = statBundle.getMinConnectivity();
		for (List<SingleResult> line : res) {
			str.append(String.format("%.2f\t", con));
			for (SingleResult cell : line) {
				str.append(String.format("%.3f\t", cell.getEfficience()));
			}
			str.append("\n");
			con += statBundle.getStepConnectivity();
		}

		// Algorithm effectivity
		str.append("\n\nAlgorithm effectivity\n");
		str.append("con\tA1\tA2\tA3\tB1\tB2\tB3\n");

		final int [] effBest = new int [6];

		con = statBundle.getMinConnectivity();
		for (List<SingleResult> line : res) {
			str.append(String.format("%.2f\t", con));

			SingleResult effMax = Collections.max(line, new Comparator<SingleResult>() {
				public int compare(SingleResult o1, SingleResult o2) {
					return Float.compare(o1.getEfficienceAlgo(), o2.getEfficienceAlgo());
				}
			});

			int i = 0;
			for (SingleResult cell : line) {
				if (Float.compare(cell.getTime(), effMax.getTime()) == 0) {
					str.append("*");
					effBest[i]++;
				}

				str.append(String.format("%.3f\t", cell.getEfficienceAlgo()));
				i++;
			}
			str.append("\n");
			con += statBundle.getStepConnectivity();
		}

		str.append("best\t");
		for (int best : effBest) {
			str.append(best + "\t");
		}

		return str.toString();
	}

	// ======================================== Best case ==========================================
	public static void makeBestExperiment(int nodeCount, String fileName) throws IOException, ClassNotFoundException {
		final FileWriter writer = new FileWriter(new File(fileName));

		StatisticsBundle statBundle;

		statBundle = initStatistBundleForBest(nodeCount).build();

		List<List<SingleResult>> res = startBestSimulation(statBundle);

		writer.append(printResultForBest(statBundle, res));
		writer.close();

		System.out.println(printResultForBest(statBundle, res));
	}

	public static StatisticsBundle.StatisticsBundleBuilder initStatistBundleForBest(int nodeCount) throws IOException, ClassNotFoundException {
		final StatisticsBundleBuilder builder = new StatisticsBundleBuilder();

		FileInputStream fis = new FileInputStream(
				new File("D:\\Other\\Yura_recovery\\PZKS\\pzks2\\src\\main\\resources\\tor")); 
		ObjectInputStream ois = new ObjectInputStream(fis); 
		final edu.kpi.pzks2.ui.system.System sysGraphBundle =
				(edu.kpi.pzks2.ui.system.System) ois.readObject(); 
		ois.close();

		Graph <Node, Edge> sysGraph = new UndirectedSparseGraph<Node, Edge>();

		int nodeId = 0;

		for (NodeDescriptor d : sysGraphBundle.getSystem().getNodes()) {
			sysGraph.addVertex(d.getNode());
			if (nodeId < d.getNode().getId()) {
				nodeId = d.getNode().getId();
			}
		}

		for (EdgeDescriptor d : sysGraphBundle.getSystem().getEdges()) {
			sysGraph.addEdge(d.getEdge(), d.getPair().getFirst(), d.getPair().getSecond());
		}

		builder.setSystem(sysGraph);

		final QueueType [] queueBuilders = {QueueType.QUEUE3};
		builder.setQueueBuilders(queueBuilders);

		final StrategyType [] strategyFactories = {StrategyType.PREEMPTIVE};
		builder.setStrategyFactories(strategyFactories);

		builder.setMinNodeCount(nodeCount);
		builder.setMaxNodeCount(nodeCount);

		builder.setMaxChannelCount(4);

		final boolean [] duplexModes = {false, true};
		builder.setDuplexModes(duplexModes);

		builder.setMaxPerformance(3);
		builder.setMinConnectivity(0.2f);
		
		builder.setMaxSpeed(3);

		return builder;
	}

	public static String printResultForBest(
			final StatisticsBundle statBundle, 
			final List<List<SingleResult>> res) {
		StringBuilder str = new StringBuilder();

		// Time
		int chans = statBundle.getMaxChannelCount() - statBundle.getMinChannelCount() + 1;
		int perfs = statBundle.getMaxPerformance() - statBundle.getMinPerformance() + 1;
		int speeds = statBundle.getMaxSpeed() - statBundle.getMinSpeed() + 1;

		for (int perf = statBundle.getMinPerformance(); perf  <= statBundle.getMaxPerformance(); perf += statBundle.getStepPerformance()) {
			for (boolean isDuplex : statBundle.getDuplexModes()) {
				for (int chanCount = statBundle.getMinChannelCount(); chanCount <= statBundle.getMaxChannelCount(); chanCount += statBundle.getStepChannelsCount()) {
					for (int speed = statBundle.getMinSpeed(); speed <= statBundle.getMaxSpeed(); speed += statBundle.getStepSpeed()) {
						int con = 0;
						str.append("Chann\tPerf\tDuplex\tSpeed\n");
						str.append(String.format("%d\t%d\t%b\t%d", chanCount, perf, isDuplex, speed));
						str.append("\ncon\ttimeP\ttimeS\tKacc\tKeff\tKeffAlgo\n");
						for (float connectivity = statBundle.getMinConnectivity();
								connectivity <= statBundle.getMaxConnectivity();
								connectivity += statBundle.getStepConnectivity()) {
							str.append(String.format("%.2f\t", connectivity));
							//						str.append(isDuplex + "\t");
							//						str.append(chanCount + "\t");
							//						str.append(perf + "\t");
	
							int r = (isDuplex ? chans * perfs * speeds : 0) + (chanCount - 1) * perfs *  speeds + (perf - 1) * speeds + speed - 1;
	
							str.append(String.format("%.3f\t", res.get(con).get(r).getTime()			));
							str.append(String.format("%.3f\t", res.get(con).get(r).getOverallWeight()	));
							str.append(String.format("%.3f\t", res.get(con).get(r).getAcceleration()	));
							str.append(String.format("%.3f\t", res.get(con).get(r).getEfficience()		));
							str.append(String.format("%.3f\t", res.get(con).get(r).getEfficienceAlgo()	));
	
							str.append("\n");
	
							con++;
						}
						str.append("\n");
					}
				}
			}
		}

		//		int conI = 0;
		//		for (float connectivity = statBundle.getMinConnectivity();
		//				connectivity <= statBundle.getMaxConnectivity();
		//				connectivity += statBundle.getStepConnectivity()) {
		//			int r = 0;
		//			for (boolean isDuplex : statBundle.getDuplexModes()) {
		//				for (int chanCount = statBundle.getMinChannelCount(); chanCount <= statBundle.getMaxChannelCount(); chanCount += statBundle.getStepChannelsCount()) {
		//					for (int perf = statBundle.getMinPerformance(); perf  <= statBundle.getMaxPerformance(); perf += statBundle.getStepPerformance()) {
		//						str.append(String.format("%.2f\t", connectivity));
		//						str.append(isDuplex + "\t");
		//						str.append(chanCount + "\t");
		//						str.append(perf + "\t");
		//						
		//						str.append(String.format("%.3f\t", res.get(conI).get(r).getTime()			));
		//						str.append(String.format("%.3f\t", res.get(conI).get(r).getOverallWeight()	));
		//						str.append(String.format("%.3f\t", res.get(conI).get(r).getAcceleration()	));
		//						str.append(String.format("%.3f\t", res.get(conI).get(r).getEfficience()		));
		//						str.append(String.format("%.3f\t", res.get(conI).get(r).getEfficienceAlgo()	));
		//						
		//						str.append("\n");
		//						r++;
		//					}
		//				}
		//			}
		//
		//			conI++;
		//		}

		return str.toString();
	}

	public static List<List<SingleResult>> startBestSimulation(StatisticsBundle statBundle) {
		final List<List<SingleResult>> generalResult = new ArrayList<List<SingleResult>>();

		for (float connectivity = statBundle.getMinConnectivity();
				connectivity <= statBundle.getMaxConnectivity();
				connectivity += statBundle.getStepConnectivity()) {
			List<List<SingleResult>> results = new ArrayList<List<SingleResult>>();
			for (int r = 0; 
					r < statBundle.getDuplexModes().length 
					* (statBundle.getMaxChannelCount() - statBundle.getMinChannelCount() + 1)
					* (statBundle.getMaxPerformance() - statBundle.getMinPerformance() + 1)
					* (statBundle.getMaxSpeed() - statBundle.getMinSpeed() + 1);
					r++) {
				results.add(new ArrayList<SingleResult>());
			}

			for (int i = 0 ; i < statBundle.getSeriesSize(); i++) {
				final GenerationParams genParams =
						new GenerationParams(
								statBundle.getMinNodeWeight(),
								statBundle.getMaxNodeWeight(),
								statBundle.getMinEdgeWeight(),
								statBundle.getMaxEdgeWeight(),
								connectivity,
								statBundle.getMaxNodeCount());

				//                System.out.print("Start GEN");
				final DirectedSparseGraph<Node, Edge> taskGraph = TaskGraphTools.generate(genParams, false);
				//                System.out.println("   Fin GEN");

				final int maxCritPath = TaskGraphTools.getMaxCriticalPath(taskGraph);
				final int overallWeight = TaskGraphTools.getOverallWeight(taskGraph);

				int r = 0;

				for (QueueType queueT : statBundle.getQueues()) {
					for (StrategyType stratT : statBundle.getStrategies()) {
						for (boolean isDuplex : statBundle.getDuplexModes()) {
							for (int chanCount = statBundle.getMinChannelCount(); chanCount <= statBundle.getMaxChannelCount(); chanCount += statBundle.getStepChannelsCount()) {
								for (int perf = statBundle.getMinPerformance(); perf  <= statBundle.getMaxPerformance(); perf += statBundle.getStepPerformance()) {
									for (int speed = statBundle.getMinSpeed(); speed <= statBundle.getMaxSpeed(); speed += statBundle.getStepSpeed()) {
										final SimulationBundleBuilder simBundleBuilder = new SimulationBundleBuilder();
										simBundleBuilder.setChanelCount(chanCount);
										simBundleBuilder.setDuplex(isDuplex);
										simBundleBuilder.setLinkSpeed(speed);
										simBundleBuilder.setProductivity(perf);
										simBundleBuilder.setPackageMode(false);
	
										simBundleBuilder.setSystem(statBundle.getSystem());
										simBundleBuilder.setTask(taskGraph);
	
										switch (queueT) {
										case QUEUE1: simBundleBuilder.setQueueBuilder(new QueueBuilder1()); break;
										case QUEUE2: simBundleBuilder.setQueueBuilder(new QueueBuilder2()); break;
										case QUEUE3: simBundleBuilder.setQueueBuilder(new QueueBuilder3()); break;
										default: break;
										}
	
										final SimulationBundle simBundle = simBundleBuilder.buildBundle();
	
										StrategyFactory strategyFactory = null;
										switch (stratT) {
										case SIMPLE: strategyFactory = new SimpleStrategy.SimpleStrategyFactory(); break;
										case PREEMPTIVE: strategyFactory = new PreemptiveStrategy.PreemptiveStrategyFactory(simBundle);
										default: break;
										}
	
										final Simulator simulator =
												new Simulator(simBundle, strategyFactory, new SimpleRouterImpl.SimpleRouterFactory());
	
										final SimulationResult simRes = simulator.start();
										results.get(r).add(
												analiseResult(simRes, statBundle.getSystem().getVertexCount(), taskGraph,
														maxCritPath, overallWeight));
	
										r++;
									}
								}
							}
						}
					}
				}
				java.lang.System.out.print(i);
			}
			java.lang.System.out.println();

			final List<SingleResult> res = new ArrayList<SingleResult>();
			for (List<SingleResult> resList : results) {
				res.add(SingleResult.getAverage(resList));
			}
			generalResult.add(res);
		}

		return generalResult;
	}
}
